import img_io;
import FFT;

import core.thread;

import std.conv;
import std.array;
import std.stdio;
import std.datetime;
import std.math;
import std.concurrency;
import std.random;
import std.algorithm;
static import std.file;

enum int PER_SECOND = 44100;
int PER_DECI_SECOND = PER_SECOND/100;

void sound_main(){
	//sound_main_playrecord_and_see();
	//sound_main_noisify();
	//sound_main_compose();
	//main_fft_test();
	//sound_main_fft_max();
	//sound_main_fft_variation();
	//sound_main_gen2();
	
	//sound_record_file(3,"/opt/sound-aauu.raw");
	foreach(i; 1..20 ){
		writeln(i);
		stdout.flush();
	  sound_file_freqs("/opt/sound-aauu.raw", PER_SECOND/10 * i);
    }
	//playTestSound();
	//playSinusSound([300:2, 600:1]); 
}

void playSinusSound(double[double] freqMap){
	double maxsum = reduce!"a+b"(0.0,freqMap.values);
	
	short[] snd = new short[PER_SECOND*1];
	foreach(i,ref s; snd){
		//if( i>6*PER_SECOND && i<6*PER_SECOND+ 20*PER_SECOND/100 ){
			double sum=0;
			foreach(f,v; freqMap){
				sum += v*sin(i*2*PI/PER_SECOND*f);
			}
			s = cast(short)( 5000*sum/maxsum );
		//}
	}
	playSound(snd);
}


void sound_file_freqs(string fname, int pos){
	short[] sound = cast(short[])std.file.read(fname);
	writeln(`sound len sec `, sound.length*1.0/PER_SECOND);
	short[] seg = sound[pos-PER_SECOND/50 .. pos];
	
	double[] fint = new double[1000];
	foreach( wave; 10 .. 500 ){
		fint[wave] = freqIntensity(seg,wave);
	}
	string plotData;
	double[double] sinusSound;
	foreach(wave; 11..499){
		plotData ~= to!string(PER_SECOND/wave) ~ ' ' ~ to!string(fint[wave]) ~ '\n';
		if( fint[wave]>=fint[wave-1] && fint[wave]>=fint[wave+1] ){
			writeln(PER_SECOND/wave, `Hz `,wave, ' ',fint[wave]);
			sinusSound[PER_SECOND/wave] = fint[wave];
		}
	}
	std.file.write("/opt/gnuplot.dat",plotData);
	
//	foreach( wave; 10 .. 500 ){
//		writeln(PER_SECOND/wave, `Hz `,wave, ' ',freqIntensity(seg,wave,1),' ',freqIntensity(seg,wave,2)/2); 
//	} 

    stdout.flush();
    playSinusSound( sinusSound );
}

real freqIntensity(short[] sound, int waveLen){
	long sumWaves = sound.length / waveLen;
	if( sumWaves < 1 ){
		return -1;
	}
	if( sumWaves>3 ) sumWaves=3;
	short[] data = sound[$ - waveLen * sumWaves .. $];
	real sumCos=0;
	real sumSin=0;
	foreach( i; 0..waveLen*sumWaves ){
		sumCos += data[i] * cos(2*PI*i/waveLen);
		sumSin += data[i] * sin(2*PI*i/waveLen);
	}
	return sqrt(sumCos*sumCos + sumSin*sumSin)/sumWaves/waveLen;
}

void sound_record_file(int seconds, string file){
	ProcessSource arecord = ProcessSource("arecord",
		["arecord", "-f", "S16_LE", "-c1", "-r44100", "-t", "raw","--buffer-time=2000"]);
	arecord.readShort(PER_SECOND);
	writeln(`say `,seconds,` seconds...`);
    stdout.flush();
    short[] sound = arecord.readShort(PER_SECOND*seconds);
	writeln(`end`);
	std.file.write(file,sound);
}

short[] genSound(double[] freqs){
	cdouble[] cfreqs = new cdouble[freqs.length*2];
	foreach( i,v; freqs ){
		cfreqs[i] = v + 0i;
		if( i>0 ){
		  cfreqs[cfreqs.length-i]=v+0i;
		}
	}
	cfreqs[cfreqs.length/2]=0+0i;
	//writeln(cfreqs.length);
	return genSound(cfreqs);
}

short[] genSound(cdouble[] cfreqs){
	cdouble[] cgen = ifft(cfreqs);
	//max(0.0, abs(cgen[0]));
	//double genmax = reduce!("max(a,abs(b)")(0.0, cgen);
	double genmax=0;
	foreach(v;cgen){
		genmax = max(genmax, abs(v));
	}	
	short[] ret = new short[cgen.length];
	if( genmax>0 ){
		foreach(i,v; cgen){
			ret[i] = cast(short)(abs(v)/genmax*20000); 
		}
	}	
	return ret;
}

void sound_main_gen2(){
//	double[] freqs = new double[16384];
//	freqs[]=0;
//	freqs[200]=1;
//	playSound(genSound(freqs));
	
	ProcessSource arecord = ProcessSource("arecord",
		["arecord", "-f", "S16_LE", "-c1", "-r44100", "-t", "raw","--buffer-time=2000"]);
	arecord.readShort(PER_SECOND);
	writeln(`say 2 seconds...`);
    stdout.flush();
    short[] sound = arecord.readShort(PER_SECOND*2);
	writeln(`end`);
    stdout.flush();
    
    sound = sound[0..0x8000];
    
   	cdouble[] dat = new cdouble[sound.length];
	foreach(i,v; sound){
		dat[i] = v + 0i;
	}
	cdouble[] cfreq = fft(dat);
	foreach(long i; 1..cfreq.length/2){
		// this changes aaaa to oooo:
//		if( i < 50/2*cfreq.length/PER_SECOND || i > 1500/2*cfreq.length/PER_SECOND ){
//		  cfreq[i] = 0+0i;
//		  cfreq[cfreq.length-i]=0+0i;
//		}
		
		bool foundPeak=false;
		double absi = abs(cfreq[i]);
		foreach(long j; 0..100 ){
//			writeln(i, ' ',j, ' ',i-j);
//			stdout.flush();
			if( i-j<0 || (j<20 && absi < abs(cfreq[i-j]) || absi < abs(cfreq[i+j])) ){
			  foundPeak=false;	
			  break;
			}else if( absi/2 > abs(cfreq[i-j]) && absi/2 > abs(cfreq[i+j]) ){
			  foundPeak=true;	
			}
		}
		if( !foundPeak ){
			  cfreq[i] = 0+0i;
			  cfreq[cfreq.length-i]=0+0i;
		}else{
			writeln("found peak ",i, ' ', abs(cfreq[i]));

		}	  
	}
	
	int countRem=0;
	foreach( v; cfreq ){
		if( abs(v)!=0 ){
			countRem++;
		}
	}
	writeln("countRem cfreq ",countRem);
	stdout.flush();
    
    //double[] mags = fftMagnitudes(sound);
    
    Thread.sleep( dur!"msecs"( 2000 ) );
    //short[] gens = genSound(mags);
    short[] gens = genSound(cfreq);
	playSound(gens ~ gens ~ gens ~ gens);

}

void sound_main_gen1(){
	double[] freqs = new double[8];
	freqs[]=0;
	freqs[2]=1;
	cdouble[] cfreqs = new cdouble[16];
	foreach( i,v; freqs ){
		cfreqs[i] = v + 0i;
		if( i>0 ){
		  cfreqs[cfreqs.length-i]=v+0i;
		}
	}
	cfreqs[cfreqs.length/2]=0+0i;
	foreach(i,v; cfreqs){
		writeln(i,` cfreqs `,v);
	}
	cdouble[] cgen = ifft(cfreqs);
	foreach(i,v; cgen){
		writeln(i,` cgen `,v);
	}
	
    	cdouble[] x = new cdouble[16];
    	foreach(i,ref v; x){
//    		v = sin(2*PI*i/x.length * 2) + 0i;
    	    v = 0+0i;
    	}
    	x[3] = 5+0i;
    	cdouble[] freqx = fft(x);
//    	foreach(i,v; freqx){
//    		writeln(i,' ',v);
//    	}
	
}

void sound_main_fft_variation(){
	ProcessSource arecord = ProcessSource("arecord",
		["arecord", "-f", "S16_LE", "-c1", "-r44100", "-t", "raw","--buffer-time=2000"]);
	arecord.readShort(PER_SECOND);
	writeln(`say 2 seconds...`);
    stdout.flush();
    short[] sound = arecord.readShort(PER_SECOND*2);
    int SEG = 4096; //PER_SECOND / 20;
    double[] magsPrev;
	for(int i=0; i<sound.length-SEG; i+=SEG){
		double[] mags = fftMagnitudes(sound[i..i+SEG]);
		if( magsPrev.length==mags.length ){
			double diff = diffMags(mags, magsPrev);
			writeln("diff ",i, " ",diff, ' ',replicate("*",cast(int)diff/100));
		}
		magsPrev=mags;
	} 	
}

double diffMags(double[] mags1, double[] mags2){
	double sum=0;
	foreach(i,v; mags1){
		if( i>10 && i<100 ){
		  sum += (v - mags2[i]) * (v - mags2[i]) / i;
		}  
	}
	return sqrt(sum) / mags1.length;
}

void sound_main_fft_max(){
	ProcessSource arecord = ProcessSource("arecord",
		["arecord", "-f", "S16_LE", "-c1", "-r44100", "-t", "raw","--buffer-time=2000"]);
	arecord.readShort(PER_SECOND);
	writeln(`say 2 seconds...`);
    stdout.flush();
    short[] sound = arecord.readShort(PER_SECOND*2);
    
    sound = sound[0..0x8000];
    double[] mags = fftMagnitudes(sound);
    foreach(i,v; mags){
    	long freq = PER_SECOND * i/sound.length;
    	if( freq < 2000 ){
    	  writeln(freq, " Hz  ", v, ' ', replicate("*",cast(int)v/100000));
    	}   
    }
    
    stdout.flush();
    try{
	    foreach(i,v; avgHarmonics(mags)){
	      long freq = PER_SECOND * i/sound.length;
	      int len=cast(int)(v/10000);
	      if( len>100 ) len=100;
	   	  writeln("sum ",freq, " Hz  ", v, ' ', len, ' ', replicate("*",len));
	    }
	}catch(Exception e){
		writeln(e);
	}    
	//writeln(freqMaximums(sound, PER_SECOND, 20));
}

double[] avgHarmonics(double[] mags){
	double[] ret = new double[1000];
	ret[] = 0;
	int[] counts = new int[ret.length];
	foreach(i, ref v; ret){
		foreach(harm; 1..7){
			long idx = i*harm;
			if( i>0 && idx<mags.length ){
				v += mags[idx];
				counts[i]++;
			}
		}
	} 
	foreach(i, ref v; ret){
		if( counts[i]>0 ){
		  v /= counts[i];
		}
	}

	return ret;
}

/*
double[] avgHarmonics(double[] mags){
	double[] ret = new double[2000];
	int[] counts = new int[ret.length];
	ret[] = 0;
	//int[] counts = new int[ret.length];
	foreach(i,v; mags){
		foreach(harm; 1..50){
			long idx = i/harm;
			if( idx<ret.length ){
			  ret[idx] += v;
			  counts[idx]++;
			}  			
		}
	}
	foreach(i, ref v; ret){
		if( counts[i]>0 ){
		  v /= counts[i];
		}
	}
	return ret;
}
*/

void sound_main_compose(){
	ProcessSource arecord = ProcessSource("arecord",
		["arecord", "-f", "S16_LE", "-c1", "-r44100", "-t", "raw","--buffer-time=2000"]);
	arecord.readShort(PER_SECOND*2);
	writeln(`say first 5 seconds...`);
    stdout.flush();
    short[] data1 = arecord.readShort(PER_SECOND*5);
	writeln(`say second 5 seconds...`);
    stdout.flush();
    short[] data2 = arecord.readShort(PER_SECOND*5);
	writeln(`now listen`);
    stdout.flush();
	arecord.readShort(PER_SECOND);
	
	short[] data = new short[data1.length];
	foreach(i,ref d; data){
		d = cast(short)((data1[i] + data2[i])/2);
	}
	playSound(data);
}

void sound_main_noisify(){
	writeln(`running arecord`);
	ProcessSource arecord = ProcessSource("arecord",
		["arecord", "-f", "S16_LE", "-c1", "-r44100", "-t", "raw","--buffer-time=2000"]);
	arecord.readShort();
	write(`reading background noise...`);
    stdout.flush();
	short[] bgNoise = arecord.readShort(PER_SECOND*3);
	double stddevBg = stddev(bgNoise);
	writeln(`END mean=`, mean(bgNoise),` stddev=`,stddevBg);
	writeln(`recording 5 seconds to noisify`);
    stdout.flush();
	
	
	double[] stddevs;
	StopWatch sw;
	sw.start();
	long oldi=0;
	foreach(long i; 0..100*5){
		short[] vals = arecord.readShort(PER_SECOND/100);
	   	  long msecs = sw.peek().msecs;
	   	  sw = StopWatch();
	   	  sw.start();
    	  double stddevi = stddev(vals);
	   	  stddevs ~= stddevi;
	   	  if( stddevi > 2*stddevBg ){
	   	  	if( oldi!=i-1 ){
	   	  		writeln();
	   	  	}
	   	  	oldi=i;
	        writeln(i,` read mean=`, mean(vals),` stddev=`,stddevi, ' ', msecs);
	        stdout.flush();
	      }
	}
	writeln(`replaying as noise`);
	playNoise(stddevs);
}

void playNoise(double[] stddevs){
	short[] snd = new short[PER_SECOND/100*stddevs.length];
	foreach(i,ref s; snd){
		s = clampToShort( gaussShort * stddevs[i/PER_DECI_SECOND] / 2000 );
	}
	playSound(snd);
}

short clampToShort(double val){
	if( val > short.max ) return short.max;
	if( val < short.min ) return short.min;
	return cast(short)val;
}

short gaussShort(){
	int sum=0;
	foreach(i; 0..12){
		sum += uniform!("[]",int,int)(-5000,5000);
	}
	return clampToShort(sum / 12);
}

void playSound(short[] sh){
  byte[] data = new byte[2*sh.length];
  short* dataAsShort = cast(short*) (data.ptr);
  foreach(i, short x; sh ){
    dataAsShort[i]=x; 
  }
  execvp_with_input("aplay",["aplay","-f", "S16_LE", "-c1", "-r44100", "-t", "raw"],data);
}


void playTestSound(){
	short[] snd = new short[PER_SECOND*10];
	foreach(i,ref s; snd){
		//if( i>6*PER_SECOND && i<6*PER_SECOND+ 20*PER_SECOND/100 ){
			s = cast(short)( 5000*sin(i*2*PI/PER_SECOND*300) );
		//}
	}
	playSound(snd);
}

void sound_main_playrecord_and_see(){
	spawn( &playTestSound );
	writeln(`running arecord`);
	ProcessSource arecord = ProcessSource("arecord",
		["arecord", "-f", "S16_LE", "-c1", "-r44100", "-t", "raw","--buffer-time=2000"]);
	arecord.readShort();
	write(`reading background noise...`);
    stdout.flush();
	short[] bgNoise = arecord.readShort(PER_SECOND*3);
	writeln(`END mean=`, mean(bgNoise),` stddev=`,stddev(bgNoise));
    stdout.flush();
	
	
	
	StopWatch sw;
	sw.start();
	long oldi=0;
	foreach(long i; 0..long.max){
		short[] vals = arecord.readShort(PER_SECOND/100);
	   	  long msecs = sw.peek().msecs;
	   	  sw = StopWatch();
	   	  sw.start();
	   	  if( stddev(vals) > 2*stddev(bgNoise) ){
	   	  	if( oldi!=i-1 ){
	   	  		writeln();
	   	  	}
	   	  	oldi=i;
	        writeln(i,` read mean=`, mean(vals),` stddev=`,stddev(vals), ' ', msecs);
	        stdout.flush();
	      }
	}
}

double stddev(short[] d){
	double m = mean(d);
	double sum = 0;
	foreach( di; d ){
		double delta = di - m;
		sum += delta*delta;
	}
	return sqrt( sum / (d.length - 1) );	
}

double mean(short[] d){
	double sum = 0;
	foreach( di; d ){
		sum += di;
	}
	return sum/d.length;
}